WebGL 片元着色器与 attribute、uniform 数据传参
一句话结论:attribute 负责逐顶点数据,uniform 负责整批共享数据,两者在顶点着色器里汇合,再通过 varying 插值送到片元着色器决定每个像素的颜色。
片元着色器与颜色输出
是什么
片元着色器是 WebGL 管线中决定每个像素颜色的可编程阶段,通过给内置变量 gl_FragColor 赋值输出最终颜色。它接收的是光栅化后每个像素对应的一组插值数据,包括颜色、UV、法线等。
解决什么问题
它解决的是“像素级效果如何实现”的问题。渐变、纹理采样、光照计算、雾效、描边等与像素密度相关的效果都必须在这一阶段完成,因为只有这里才能拿到当前像素的坐标与插值属性。
为什么这样做
颜色写入放在光栅化之后,是因为像素数量远多于顶点,逐像素计算才能表现细节。GPU 为每个像素独立执行同一份着色器,插值由固定硬件完成,因此代码只需按单个像素的视角写逻辑,不必关心三角形覆盖了哪些像素。
怎么用
// 顶点着色器:把颜色作为 varying 传出
attribute vec3 aPosition;
attribute vec3 aColor;
varying vec3 vColor;
void main() {
vColor = aColor;
gl_Position = vec4(aPosition, 1.0);
}
// 片元着色器:接收插值颜色并输出
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
常见坑
- 片元着色器不写精度限定符 → 原因:GLSL ES 要求浮点默认精度 → 处理:首行声明
precision mediump float; - 输出颜色出现色带 → 原因:
mediump精度不足 → 处理:颜色计算改用highp - 以为 varying 会做线性插值 → 原因:插值按透视校正执行 → 处理:需要屏幕空间线性插值时改用
noperspective或手动除 w - 变量名在两类着色器中不一致 → 原因:varying 名称必须完全一致 → 处理:统一命名并集中管理
attribute 与 uniform 的区别
是什么
attribute 与 uniform 是 JavaScript 向着色器传值的两种通道。attribute 描述逐顶点属性,每个顶点可以取不同值;uniform 描述整次绘制共享的常量,同一批顶点与像素读取同一份数据。
解决什么问题
两者分离解决了“数据更新频率不同”的矛盾。顶点位置、颜色、法线逐顶点变化,必须逐个传入;而视图矩阵、光源位置、时间这类整批一致的数据如果也逐顶点传,会造成大量冗余传输与状态设置开销。
为什么这样做
GPU 在执行着色器时不区分“变量来源”,但驱动程序需要预知数据的组织方式才能高效批量传输。attribute 按顶点顺序打包成缓冲,可以一次性上传并驻留显存;uniform 则存放在常量区,在绘制调用前设置一次即可被所有执行单元读取。
怎么用
| 对比项 | attribute | uniform |
|---|---|---|
| 数据粒度 | 逐顶点 | 每次绘制调用一份 |
| 可用阶段 | 仅顶点着色器 | 顶点与片元着色器 |
| 典型内容 | 位置、法线、UV、顶点色 | 矩阵、光照参数、时间、纹理采样器 |
| 更新方式 | 绑定缓冲并设置 vertexAttribPointer | 绘制前调用 uniformMatrix4fv 等 |
| 数量上限 | 受 MAX_VERTEX_ATTRIBS 限制 | 受 MAX_VERTEX_UNIFORM_VECTORS 限制 |
// attribute:绑定顶点位置
const loc = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
// uniform:每帧设置一次 MVP 矩阵
const uLoc = gl.getUniformLocation(program, 'uMvpMatrix');
gl.uniformMatrix4fv(uLoc, false, mvpMatrix);
常见坑
- 用 uniform 传逐顶点数据 → 原因:需求混淆 → 处理:颜色渐变、每顶点偏移一律改用 attribute 加 varying
- 忘记
enableVertexAttribArray→ 原因:顶点属性默认关闭 → 处理:绑定后立即启用,程序切换后重新获取位置 - uniform 位置为 null 仍继续赋值 → 原因:变量被编译器优化掉 → 处理:判断返回值,未使用的 uniform 直接跳过
- 在片元着色器声明 attribute → 原因:GLSL ES 1.00 不允许 → 处理:attribute 只在顶点阶段声明,跨阶段数据用 varying
要点总结
- 片元着色器必须给
gl_FragColor赋值,并声明浮点精度限定符 - varying 由顶点着色器写入、片元着色器读取,中间由硬件插值
- attribute 是逐顶点数据,只能在顶点着色器使用
- uniform 是整批共享数据,两类着色器都能读取,绘制前设置一次
- 顶点属性用完要启用,uniform 位置可能被优化为空,使用前需判空
